﻿
package extremefx.ui.dnd {
	import extremefx.conditions.ensuresBool;	
	import extremefx.conditions.requires;	
	import extremefx.events.EventHandler;
	import extremefx.ui.Component;
	import extremefx.ui.UIStage;
	
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.utils.Dictionary;		

	/**
	 * @author Marcelo Volmaro
	 */
	public final class DragManager {
		private static var _inst:DragManager;
		private var _stage:Stage;
		private var _visuals:Sprite;
		private var _isDragging:Boolean;
		private var _dragInitiator:IDragSource;
		private var _sourceData:SourceData;
		private var _dragImage:IDraggingImage;
		private var _dragMC:Sprite;
		private var _dragListener:IDropListener;
		private var _eOnDragStart:EventHandler;
		private var _eOnDragEnter:EventHandler;
		private var _eOnDragOverring:EventHandler;
		private var _eOnDragExit:EventHandler;
		private var _eOnDragDrop:EventHandler;
		private var _dropCList:Dictionary;
		private var _enteredComponent:IDropListener;

		public function DragManager(pRes:PrivateClass) {
			_stage = UIStage.STAGE;
			_visuals = UIStage.DRAGNDROP;
			_dropCList = Component.componentList;
			
			_eOnDragStart = new EventHandler(this);
			_eOnDragEnter = new EventHandler(this);
			_eOnDragOverring = new EventHandler(this);
			_eOnDragExit = new EventHandler(this);
			_eOnDragDrop = new EventHandler(this);
		}
		
		public static function get instance():DragManager{
			if (_inst == null){
				_inst = new DragManager(new PrivateClass());
			}
			
			return _inst;
		}
		
		public static function startDrag(pDragInitiator:Component, pSourceData:SourceData = null, pDragImage:IDraggingImage = null, pDragListener:IDropListener=null):void{
			requires(pDragInitiator, "dragInitiator").isOfType(IDragSource, "Invalid drag source supplied to DragManager");
			
			instance._startDragdragInitiator(pDragInitiator, pSourceData, pDragImage, pDragListener);
		}
		
		public static function getTargetComponent():Component {
			var dobj:DisplayObject = getTarget(null, null, instance._checkdropC);
			if (dobj) return Component.componentList[dobj];
			return null;
		}
		
		public static function getDropTargetComponent():IDropListener {
			var dobj:DisplayObject = getTarget(null, null, instance._checkdropCT);
			if (dobj) return Component.componentList[dobj];
			return null;
		}
		
		private function _checkdropC(pD:DisplayObject):Boolean {
			return _dropCList[pD] is Component;
		}
		
		private function _checkdropCT(pD:DisplayObject):Boolean {
			return _dropCList[pD] is IDropListener; 
		}

		public static function getTarget(pPos:Point = null, pTargetType:Class = null, pAdditionalCheck:Function = null):DisplayObject {
			var d:DragManager = instance;
			
			pTargetType = pTargetType || DisplayObject;
			pPos = pPos || new Point(int(d._stage.mouseX), int(d._stage.mouseY));

			var targets:Array = d._stage.getObjectsUnderPoint(pPos),
			dragMC:Sprite = d._dragMC,
			n:int = targets.length;
			
			for(var i:int=n-1; i>=0; i--){
				var tg:DisplayObject = targets[i];
				if(tg is pTargetType && tg != dragMC && !dragMC.contains(tg)){
					if (pAdditionalCheck == null){
						return tg;
						
					} else if (pAdditionalCheck(tg)){
						return tg;
					}
				}
			}
			
			return null;
		}
		
		private function addDragListener(pListener:IDropListener):void{
			_eOnDragStart.add(pListener.dragStart);
			_eOnDragEnter.add(pListener.dragEnter);
			_eOnDragOverring.add(pListener.dragOverring);
			_eOnDragExit.add(pListener.dragExit);
			_eOnDragDrop.add(pListener.dragDrop);
		}
	
		private function removeDragListener(pListener:IDropListener):void{
			_eOnDragStart.remove(pListener.dragStart);
			_eOnDragEnter.remove(pListener.dragEnter);
			_eOnDragOverring.remove(pListener.dragOverring);
			_eOnDragExit.remove(pListener.dragExit);
			_eOnDragDrop.remove(pListener.dragDrop);
		}
		
		private function _startDragdragInitiator(pDragInitiator:Component, pSourceData:SourceData, pDragImage:IDraggingImage, pDragListener:IDropListener):void {
			ensuresBool(_isDragging).isFalse("The last dragging action is not finished, can't start a new one!");

			if(pDragImage == null){
				pDragImage = new DefaultDragImage(pDragInitiator);
			}
			
			_isDragging = true;
			_dragListener = pDragListener;
			
			if (pDragListener) addDragListener(pDragListener);
			
			_dragInitiator = pDragInitiator as IDragSource;
			_sourceData = pSourceData;
			
			if (_dragImage) _dragImage.dispose();
			_dragImage = pDragImage;
			
			if (_dragMC){
				if (_dragMC.parent) _dragMC.parent.removeChild(_dragMC);
			}
			
			var dmc:Sprite = _dragMC = new Sprite();
			
			_visuals.addChild(dmc);
			_dropState(false);
			
			dmc.x = int(_stage.mouseX - pDragInitiator.sprite.mouseX);
			dmc.y = int(_stage.mouseY - pDragInitiator.sprite.mouseY);
			dmc.startDrag(false);
			
			_dragInitiator.onDragStart.fire(new DragEvent(dmc.x, dmc.y));
			_eOnDragStart.fire(new DropEvent(_dragInitiator, _sourceData, dmc.x, dmc.y));
			_enteredComponent = null;
			
			_stage.addEventListener(MouseEvent.MOUSE_MOVE, _onMouseMove, false, 0, true);
			_stage.addEventListener(MouseEvent.MOUSE_DOWN, _drop, false, 0, true);
			_stage.addEventListener(MouseEvent.MOUSE_UP, _drop, false, 0, true);
			
			_onMouseMove(null);
		}
		
		private function _drop(event:MouseEvent):void {
			_dragMC.stopDrag();
			_stage.removeEventListener(MouseEvent.MOUSE_MOVE, _onMouseMove);
			_stage.removeEventListener(MouseEvent.MOUSE_DOWN, _drop);
			_stage.removeEventListener(MouseEvent.MOUSE_UP, _drop);
			_isDragging = false;
			
			if (_dragListener) removeDragListener(_dragListener);
			if (_dragImage) _dragImage.dispose();
			
			_dragInitiator.onDragStop.fire(new DragEvent(int(_stage.mouseX), int(_stage.mouseY)));
			
			_dragInitiator = null;
			_dragListener = null;
			_dragImage = null;
			_sourceData = null;
		}

		private function _onMouseMove(event:MouseEvent):void {
			var x:int = int(_stage.mouseX);
			var y:int = int(_stage.mouseY);
			
			var dobj:DisplayObject = getTarget(null, null, instance._checkdropCT),
			dropC:IDropListener = null;
			
			if (dobj) dropC = _dropCList[dobj];
			
			if (dropC == _enteredComponent){
				if (_enteredComponent){
					_eOnDragOverring.fire(new DropEvent(_dragInitiator, _sourceData, x, y, _enteredComponent));
					_enteredComponent.dragOverring(new DropEvent(_dragInitiator, _sourceData, x, y));
				}
				
			} else {
				if (_enteredComponent){
					_dropState(false);
					_eOnDragExit.fire(new DropEvent(_dragInitiator, _sourceData, x, y, _enteredComponent, dropC));
					_enteredComponent.dragExit(new DropEvent(_dragInitiator, _sourceData, x, y, dropC));
				}
				
				if (dropC){
					if(dropC.acceptDragSource(_dragInitiator as IDragSource, _sourceData)){
						_dropState(true);
					}
					
					_eOnDragEnter.fire(new DropEvent(_dragInitiator, _sourceData, x, y, dropC, _enteredComponent));
					dropC.dragEnter(new DropEvent(_dragInitiator, _sourceData, x, y, _enteredComponent));
				}
				
				_enteredComponent = dropC;
			}
			
			_dragInitiator.onDragMove.fire(new DragEvent(x, y));
		}

		private function _dropState(pState:Boolean):void {
			var img:DisplayObject = pState ? _dragImage.acceptImage : _dragImage.rejectImage;
			
			if (_dragMC.numChildren > 0){
				_dragMC.removeChildAt(0);
			}
			
			if (img is DisplayObject){
				_dragMC.addChild(img);
			}
		}
	}
}

final class PrivateClass {}